/* Copyright JS Foundation and other contributors, http://js.foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "config.h"
#include "jerryscript.h"

#include "test-common.h"

/**
 * Maximum size of snapshots buffer
 */
#define SNAPSHOT_BUFFER_SIZE (256)

/**
 * Maximum size of literal buffer
 */
#define LITERAL_BUFFER_SIZE (256)

/**
 * Magic strings
 */
static const jerry_char_t *magic_strings[] =
{
  (const jerry_char_t *) " ",
  (const jerry_char_t *) "a",
  (const jerry_char_t *) "b",
  (const jerry_char_t *) "c",
  (const jerry_char_t *) "from",
  (const jerry_char_t *) "func",
  (const jerry_char_t *) "string",
  (const jerry_char_t *) "snapshot"
};

/**
 * Magic string lengths
 */
static const jerry_length_t magic_string_lengths[] =
{
  1, 1, 1, 1, 4, 4, 6, 8
};

static void test_function_snapshot (void)
{
  /* function to snapshot */
  if (!jerry_is_feature_enabled (JERRY_FEATURE_SNAPSHOT_SAVE)
      || !jerry_is_feature_enabled (JERRY_FEATURE_SNAPSHOT_EXEC))
  {
    return;
  }

  const jerry_init_flag_t flags = JERRY_INIT_EMPTY;
  static uint32_t function_snapshot_buffer[SNAPSHOT_BUFFER_SIZE];

  const jerry_char_t func_args[] = "a, b";
  const jerry_char_t code_to_snapshot[] = "return a + b";

  jerry_init (flags);
  jerry_value_t generate_result;
  generate_result = jerry_generate_function_snapshot (NULL,
                                                      0,
                                                      code_to_snapshot,
                                                      sizeof (code_to_snapshot) - 1,
                                                      func_args,
                                                      sizeof (func_args) - 1,
                                                      0,
                                                      function_snapshot_buffer,
                                                      SNAPSHOT_BUFFER_SIZE);
  TEST_ASSERT (!jerry_value_is_error (generate_result)
               && jerry_value_is_number (generate_result));

  size_t function_snapshot_size = (size_t) jerry_get_number_value (generate_result);
  jerry_release_value (generate_result);

  jerry_cleanup ();

  jerry_init (flags);

  jerry_value_t function_obj = jerry_load_function_snapshot (function_snapshot_buffer,
                                                             function_snapshot_size,
                                                             0,
                                                             0);

  TEST_ASSERT (!jerry_value_is_error (function_obj));
  TEST_ASSERT (jerry_value_is_function (function_obj));

  jerry_value_t this_val = jerry_create_undefined ();
  jerry_value_t args[2];
  args[0] = jerry_create_number (1.0);
  args[1] = jerry_create_number (2.0);

  jerry_value_t res = jerry_call_function (function_obj, this_val, args, 2);

  TEST_ASSERT (!jerry_value_is_error (res));
  TEST_ASSERT (jerry_value_is_number (res));
  double num = jerry_get_number_value (res);
  TEST_ASSERT (num == 3);

  jerry_release_value (args[0]);
  jerry_release_value (args[1]);
  jerry_release_value (res);
  jerry_release_value (function_obj);

  jerry_cleanup ();
} /* test_function_snapshot */

static void arguments_test_exec_snapshot (uint32_t *snapshot_p, size_t snapshot_size, uint32_t exec_snapshot_flags)
{
  jerry_init (JERRY_INIT_EMPTY);
  jerry_value_t res = jerry_exec_snapshot (snapshot_p, snapshot_size, 0, exec_snapshot_flags);
  TEST_ASSERT (!jerry_value_is_error (res));
  TEST_ASSERT (jerry_value_is_number (res));
  double raw_value = jerry_get_number_value (res);
  TEST_ASSERT (raw_value == 15);
  jerry_release_value (res);

  jerry_cleanup ();
} /* arguments_test_exec_snapshot */

static void test_function_arguments_snapshot (void)
{
  if (jerry_is_feature_enabled (JERRY_FEATURE_SNAPSHOT_SAVE)
      && jerry_is_feature_enabled (JERRY_FEATURE_SNAPSHOT_EXEC))
  {
    static uint32_t arguments_snapshot_buffer[SNAPSHOT_BUFFER_SIZE];

    const jerry_char_t code_to_snapshot[] = TEST_STRING_LITERAL (
      "function f(a,b,c) {"
      "  arguments[0]++;"
      "  arguments[1]++;"
      "  arguments[2]++;"
      "  return a + b + c;"
      "}"
      "f(3,4,5);"
    );
    jerry_init (JERRY_INIT_EMPTY);

    jerry_value_t generate_result;
    generate_result = jerry_generate_snapshot (NULL,
                                               0,
                                               code_to_snapshot,
                                               sizeof (code_to_snapshot) - 1,
                                               0,
                                               arguments_snapshot_buffer,
                                               SNAPSHOT_BUFFER_SIZE);

    TEST_ASSERT (!jerry_value_is_error (generate_result)
                 && jerry_value_is_number (generate_result));

    size_t snapshot_size = (size_t) jerry_get_number_value (generate_result);
    jerry_release_value (generate_result);

    jerry_cleanup ();

    arguments_test_exec_snapshot (arguments_snapshot_buffer, snapshot_size, 0);
    arguments_test_exec_snapshot (arguments_snapshot_buffer, snapshot_size, JERRY_SNAPSHOT_EXEC_COPY_DATA);
  }
} /* test_function_arguments_snapshot */

static void test_exec_snapshot (uint32_t *snapshot_p, size_t snapshot_size, uint32_t exec_snapshot_flags)
{
  char string_data[32];

  jerry_init (JERRY_INIT_EMPTY);

  jerry_register_magic_strings (magic_strings,
                                sizeof (magic_string_lengths) / sizeof (jerry_length_t),
                                magic_string_lengths);

  jerry_value_t res = jerry_exec_snapshot (snapshot_p, snapshot_size, 0, exec_snapshot_flags);

  TEST_ASSERT (!jerry_value_is_error (res));
  TEST_ASSERT (jerry_value_is_string (res));
  jerry_size_t sz = jerry_get_string_size (res);
  TEST_ASSERT (sz == 20);
  sz = jerry_string_to_char_buffer (res, (jerry_char_t *) string_data, sz);
  TEST_ASSERT (sz == 20);
  jerry_release_value (res);
  TEST_ASSERT (!strncmp (string_data, "string from snapshot", (size_t) sz));

  jerry_cleanup ();
} /* test_exec_snapshot */

int
main (void)
{
  static uint32_t snapshot_buffer[SNAPSHOT_BUFFER_SIZE];

  TEST_INIT ();

  /* Dump / execute snapshot */
  if (jerry_is_feature_enabled (JERRY_FEATURE_SNAPSHOT_SAVE)
      && jerry_is_feature_enabled (JERRY_FEATURE_SNAPSHOT_EXEC))
  {
    const jerry_char_t code_to_snapshot[] = "(function () { return 'string from snapshot'; }) ();";

    jerry_init (JERRY_INIT_EMPTY);
    jerry_value_t generate_result;
    generate_result = jerry_generate_snapshot (NULL,
                                               0,
                                               code_to_snapshot,
                                               sizeof (code_to_snapshot) - 1,
                                               0,
                                               snapshot_buffer,
                                               SNAPSHOT_BUFFER_SIZE);
    TEST_ASSERT (!jerry_value_is_error (generate_result)
                 && jerry_value_is_number (generate_result));

    size_t snapshot_size = (size_t) jerry_get_number_value (generate_result);
    jerry_release_value (generate_result);

    /* Check the snapshot data. Unused bytes should be filled with zeroes */
    const uint8_t expected_data[] =
    {
      0x4A, 0x52, 0x52, 0x59, 0x2C, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00,
      0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
      0x03, 0x00, 0x01, 0x00, 0x41, 0x00, 0x01, 0x00,
      0x00, 0x00, 0x00, 0x01, 0x18, 0x00, 0x00, 0x00,
      0x2C, 0x00, 0xC9, 0x53, 0x00, 0x00, 0x00, 0x00,
      0x03, 0x00, 0x01, 0x00, 0x41, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x01, 0x01, 0x07, 0x00, 0x00, 0x00,
      0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x14, 0x00, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67,
      0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x73, 0x6E,
      0x61, 0x70, 0x73, 0x68, 0x6F, 0x74,
    };

    if (sizeof (expected_data) != snapshot_size || memcmp (expected_data, snapshot_buffer, sizeof (expected_data)))
    {
      printf ("Snapshot data has been changed, please update tests/unit-core/test-snapshot.c.\n");
      printf ("-------------------------------------------------------------------------------\n");
      printf ("    const uint8_t expected_data[] =\n");
      printf ("    {");
      for (unsigned int i = 0; i < snapshot_size; i++)
      {
        if ((i % 8) == 0)
        {
          printf ("\n     ");
        }
        printf (" 0x%02X,", ((uint8_t *) snapshot_buffer)[i]);
      }
      printf ("\n    };\n");
      printf ("-------------------------------------------------------------------------------\n");
    }

    TEST_ASSERT (sizeof (expected_data) == snapshot_size);
    TEST_ASSERT (0 == memcmp (expected_data, snapshot_buffer, sizeof (expected_data)));

    jerry_cleanup ();

    test_exec_snapshot (snapshot_buffer, snapshot_size, 0);
    test_exec_snapshot (snapshot_buffer, snapshot_size, JERRY_SNAPSHOT_EXEC_COPY_DATA);
  }

  /* Static snapshot */
  if (jerry_is_feature_enabled (JERRY_FEATURE_SNAPSHOT_SAVE)
      && jerry_is_feature_enabled (JERRY_FEATURE_SNAPSHOT_EXEC))
  {
    const jerry_char_t code_to_snapshot[] = TEST_STRING_LITERAL (
      "function func(a, b, c) {"
      "  c = 'snapshot';"
      "  return arguments[0] + ' ' + b + ' ' + arguments[2];"
      "};"
      "func('string', 'from');"
    );

    jerry_init (JERRY_INIT_EMPTY);
    jerry_register_magic_strings (magic_strings,
                                  sizeof (magic_string_lengths) / sizeof (jerry_length_t),
                                  magic_string_lengths);

    jerry_value_t generate_result;
    generate_result = jerry_generate_snapshot (NULL,
                                               0,
                                               code_to_snapshot,
                                               sizeof (code_to_snapshot) - 1,
                                               JERRY_SNAPSHOT_SAVE_STATIC,
                                               snapshot_buffer,
                                               SNAPSHOT_BUFFER_SIZE);
    TEST_ASSERT (!jerry_value_is_error (generate_result)
                 && jerry_value_is_number (generate_result));

    size_t snapshot_size = (size_t) jerry_get_number_value (generate_result);
    jerry_release_value (generate_result);

    /* Static snapshots are not supported by default. */
    jerry_value_t exec_result = jerry_exec_snapshot (snapshot_buffer, snapshot_size, 0, 0);
    TEST_ASSERT (jerry_value_is_error (exec_result));
    jerry_release_value (exec_result);

    jerry_cleanup ();

    test_exec_snapshot (snapshot_buffer, snapshot_size, JERRY_SNAPSHOT_EXEC_ALLOW_STATIC);
  }

  /* Merge snapshot */
  if (jerry_is_feature_enabled (JERRY_FEATURE_SNAPSHOT_SAVE)
      && jerry_is_feature_enabled (JERRY_FEATURE_SNAPSHOT_EXEC))
  {
    static uint32_t snapshot_buffer_0[SNAPSHOT_BUFFER_SIZE];
    static uint32_t snapshot_buffer_1[SNAPSHOT_BUFFER_SIZE];
    size_t snapshot_sizes[2];
    static uint32_t merged_snapshot_buffer[SNAPSHOT_BUFFER_SIZE];

    const jerry_char_t code_to_snapshot1[] = "var a = 'hello'; 123";

    jerry_init (JERRY_INIT_EMPTY);
    jerry_value_t generate_result;
    generate_result = jerry_generate_snapshot (NULL,
                                               0,
                                               code_to_snapshot1,
                                               sizeof (code_to_snapshot1) - 1,
                                               0,
                                               snapshot_buffer_0,
                                               SNAPSHOT_BUFFER_SIZE);
    TEST_ASSERT (!jerry_value_is_error (generate_result)
                 && jerry_value_is_number (generate_result));

    snapshot_sizes[0] = (size_t) jerry_get_number_value (generate_result);
    jerry_release_value (generate_result);

    jerry_cleanup ();

    const jerry_char_t code_to_snapshot2[] = "var b = 'hello'; 456";

    jerry_init (JERRY_INIT_EMPTY);
    generate_result = jerry_generate_snapshot (NULL,
                                               0,
                                               code_to_snapshot2,
                                               sizeof (code_to_snapshot2) - 1,
                                               0,
                                               snapshot_buffer_1,
                                               SNAPSHOT_BUFFER_SIZE);
    TEST_ASSERT (!jerry_value_is_error (generate_result)
                 && jerry_value_is_number (generate_result));

    snapshot_sizes[1] = (size_t) jerry_get_number_value (generate_result);
    jerry_release_value (generate_result);

    jerry_cleanup ();

    jerry_init (JERRY_INIT_EMPTY);

    const char *error_p;
    const uint32_t *snapshot_buffers[2];

    snapshot_buffers[0] = snapshot_buffer_0;
    snapshot_buffers[1] = snapshot_buffer_1;

    static uint32_t snapshot_buffer_0_bck[SNAPSHOT_BUFFER_SIZE];
    static uint32_t snapshot_buffer_1_bck[SNAPSHOT_BUFFER_SIZE];

    memcpy (snapshot_buffer_0_bck, snapshot_buffer_0, SNAPSHOT_BUFFER_SIZE);
    memcpy (snapshot_buffer_1_bck, snapshot_buffer_1, SNAPSHOT_BUFFER_SIZE);

    size_t merged_size = jerry_merge_snapshots (snapshot_buffers,
                                                snapshot_sizes,
                                                2,
                                                merged_snapshot_buffer,
                                                SNAPSHOT_BUFFER_SIZE,
                                                &error_p);

    jerry_cleanup ();

    TEST_ASSERT (0 == memcmp (snapshot_buffer_0_bck, snapshot_buffer_0, SNAPSHOT_BUFFER_SIZE));
    TEST_ASSERT (0 == memcmp (snapshot_buffer_1_bck, snapshot_buffer_1, SNAPSHOT_BUFFER_SIZE));

    jerry_init (JERRY_INIT_EMPTY);

    jerry_value_t res = jerry_exec_snapshot (merged_snapshot_buffer, merged_size, 0, 0);
    TEST_ASSERT (!jerry_value_is_error (res));
    TEST_ASSERT (jerry_get_number_value (res) == 123);
    jerry_release_value (res);

    res = jerry_exec_snapshot (merged_snapshot_buffer, merged_size, 1, 0);
    TEST_ASSERT (!jerry_value_is_error (res));
    TEST_ASSERT (jerry_get_number_value (res) == 456);
    jerry_release_value (res);

    jerry_cleanup ();
  }

  /* Save literals */
  if (jerry_is_feature_enabled (JERRY_FEATURE_SNAPSHOT_SAVE))
  {
    /* C format generation */
    jerry_init (JERRY_INIT_EMPTY);

    static jerry_char_t literal_buffer_c[LITERAL_BUFFER_SIZE];
    static uint32_t literal_snapshot_buffer[SNAPSHOT_BUFFER_SIZE];
    static const jerry_char_t code_for_c_format[] = "var object = { aa:'fo\" o\\n \\\\', Bb:'max', aaa:'xzy0' };";

    jerry_value_t generate_result;
    generate_result = jerry_generate_snapshot (NULL,
                                               0,
                                               code_for_c_format,
                                               sizeof (code_for_c_format) - 1,
                                               0,
                                               literal_snapshot_buffer,
                                               SNAPSHOT_BUFFER_SIZE);

    TEST_ASSERT (!jerry_value_is_error (generate_result));
    TEST_ASSERT (jerry_value_is_number (generate_result));

    size_t snapshot_size = (size_t) jerry_get_number_value (generate_result);
    jerry_release_value (generate_result);

    /* In ES2015 we emit extra bytecode instructions to check global variable redeclaration. */
    const size_t expected_size = (jerry_is_feature_enabled (JERRY_FEATURE_SYMBOL)) ? 132 : 124;
    TEST_ASSERT (snapshot_size == expected_size);

    const size_t lit_c_buf_sz = jerry_get_literals_from_snapshot (literal_snapshot_buffer,
                                                                  snapshot_size,
                                                                  literal_buffer_c,
                                                                  LITERAL_BUFFER_SIZE,
                                                                  true);
    TEST_ASSERT (lit_c_buf_sz == 239);

    static const char *expected_c_format = (
                                            "jerry_length_t literal_count = 5;\n\n"
                                            "jerry_char_t *literals[5] =\n"
                                            "{\n"
                                            "  \"Bb\",\n"
                                            "  \"aa\",\n"
                                            "  \"aaa\",\n"
                                            "  \"xzy0\",\n"
                                            "  \"fo\\\" o\\x0A \\\\\"\n"
                                            "};\n\n"
                                            "jerry_length_t literal_sizes[5] =\n"
                                            "{\n"
                                            "  2 /* Bb */,\n"
                                            "  2 /* aa */,\n"
                                            "  3 /* aaa */,\n"
                                            "  4 /* xzy0 */,\n"
                                            "  8 /* fo\" o\n \\ */\n"
                                            "};\n"
                                            );

    TEST_ASSERT (!strncmp ((char *) literal_buffer_c, expected_c_format, lit_c_buf_sz));

    /* List format generation */
    static jerry_char_t literal_buffer_list[LITERAL_BUFFER_SIZE];
    const size_t lit_list_buf_sz = jerry_get_literals_from_snapshot (literal_snapshot_buffer,
                                                                     snapshot_size,
                                                                     literal_buffer_list,
                                                                     LITERAL_BUFFER_SIZE,
                                                                     false);
    TEST_ASSERT (lit_list_buf_sz == 34);
    TEST_ASSERT (!strncmp ((char *) literal_buffer_list,
                           "2 Bb\n2 aa\n3 aaa\n4 xzy0\n8 fo\" o\n \\\n",
                           lit_list_buf_sz));

    jerry_cleanup ();
  }

  test_function_snapshot ();

  test_function_arguments_snapshot ();

  return 0;
} /* main */
